package com.paradise.rsasign.utils;

import com.alibaba.fastjson.JSON;
import org.apache.tomcat.util.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;


/**
 * RSA工具类
 *
 * @author nz
 * @date 2018年4月22日 下午3:53:41
 */
public class RsaKeyUtil {


    private RsaKeyUtil() {
    }

    /**
     * 初始化密钥对
     *
     * @param keySize 密钥长度
     * @return KeyPair    返回类型
     */
    public static KeyPair generateRsaKeyPair(int keySize) {
        KeyPairGenerator generator = null;
        SecureRandom random = new SecureRandom();
        Security.addProvider(new BouncyCastleProvider());
        try {
            generator = KeyPairGenerator.getInstance("RSA", "BC");
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
        }
        assert generator != null;
        generator.initialize(keySize, random);
        return generator.generateKeyPair();
    }


    /**
     * RSA私钥签名
     *
     * @param data       数据
     * @param privateKey 私钥
     * @return byte[]    返回类型
     */
    public static byte[] sign(String data, byte[] privateKey) {
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKey);
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey privateKey2 = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
            Signature signature = Signature.getInstance("SHA1WithRSA");
            signature.initSign(privateKey2);
            signature.update(data.getBytes(StandardCharsets.UTF_8));
            return signature.sign();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new byte[]{};
    }

    /**
     * 校验签名是否正确
     *
     * @param data           数据
     * @param publicKey      公钥
     * @param signatureBytes 私钥签名后的数据
     * @return boolean    返回类型
     */
    public static boolean verify(String data, byte[] publicKey, byte[] signatureBytes) {
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKey);

        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PublicKey publicKey2 = keyFactory.generatePublic(x509EncodedKeySpec);

            Signature signature = Signature.getInstance("SHA1WithRSA");
            signature.initVerify(publicKey2);
            signature.update(data.getBytes(StandardCharsets.UTF_8));
            return signature.verify(signatureBytes);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    // 后台测试签名的时候 要和前台保持一致，所以需要将结果转换
    public static String bytesToHexString(byte[] bytes) {
        StringBuilder string = new StringBuilder();
        for (byte b : bytes) {
            String hexString = Integer.toHexString(0x00FF & b);
            string.append(hexString.length() == 1 ? "0" + hexString : hexString);
        }
        return string.toString();
    }

    // 前台的签名结果是将byte中的一些 负数转换成了正数，
    // 但是后台验证的方法需要的又必须是转换之前的
    public static byte[] hexStringToBytes(String data) {
        int k = 0;
        byte[] results = new byte[data.length() / 2];
        for (int i = 0; i + 1 < data.length(); i += 2, k++) {
            results[k] = (byte) (Character.digit(data.charAt(i), 16) << 4);
            results[k] += (byte) (Character.digit(data.charAt(i + 1), 16));
        }
        return results;
    }

    public static byte[] decryptBASE64(String key) {
        return Base64.decodeBase64(key);
    }

    public static String encryptBASE64(byte[] bytes) {
        return Base64.encodeBase64String(bytes);
    }


    /**
     * 使用模和指数生成RSA公钥
     * 注意：【此代码用了默认补位方式，为RSA/None/PKCS1Padding，不同JDK默认的补位方式可能不同，如Android默认是RSA/None/NoPadding】
     *
     * @param modulus     模
     * @param pubExponent 指数
     * @return string 公钥
     */
    public static String getPublicKey(String modulus, String pubExponent) {
        try {
            BigInteger b1 = new BigInteger(modulus);
            BigInteger b2 = new BigInteger(pubExponent);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            RSAPublicKeySpec keySpec = new RSAPublicKeySpec(b1, b2);
            return bytesToHexString(((RSAPublicKey) keyFactory.generatePublic(keySpec)).getEncoded());
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 使用模和指数生成RSA私钥
     * 注意：【此代码用了默认补位方式，为RSA/None/PKCS1Padding，不同JDK默认的补位方式可能不同，如Android默认是RSA/None/NoPadding】
     *
     * @param modulus     模
     * @param pubExponent 指数
     * @return String pk
     */
    public static String getPrivateKey(String modulus, String pubExponent) {
        try {
            BigInteger b1 = new BigInteger(modulus);
            BigInteger b2 = new BigInteger(pubExponent);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(b1, b2);
            return bytesToHexString(((RSAPrivateKey) keyFactory.generatePrivate(keySpec)).getEncoded());
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static void main(String[] args) {
        // appId 对应的公私钥
        String privateKey = "MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEA7WjnCTU9rhLitFbz\n" +
                "cssCpy4SOBV48U4tzJpr7VGXpOGs4XXIVvCSzZSQan6kL6lJV39jIm2aNQItU8or\n" +
                "frsrAQIDAQABAkBA7TZ6AzB6IboUPc9YboKsO+JJqj2oKIxH71di0LSbJ4HrMuzs\n" +
                "zuLynPbJ+FU3mqQ1EROLtb7NHexUAeF0JXh9AiEA9trn/YuNrfYSJScCVnG/4BI2\n" +
                "xRrEIav5p5Oiu27U2bMCIQD2NGt7WryGdvvoPl7R+k29bqE8kEcfc8FMqUFBYkIm\n" +
                "ewIgH5RhmU4BEgAo0hfrdKOYqFGsMAr8jFIz3fxFFTVYhYECIGHPaXPUFFmHI4Sc\n" +
                "YqcgvYmoTb31w4unPP/rrdT/6C/JAiEA8NgVGeNZCeJIkGCUgzgboO+39nZpfNaY\n" +
                "XzhcWJveuv0=";
        String publicKey = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAO1o5wk1Pa4S4rRW83LLAqcuEjgVePFO\n" +
                "Lcyaa+1Rl6ThrOF1yFbwks2UkGp+pC+pSVd/YyJtmjUCLVPKK367KwECAwEAAQ==";
        // 模拟 body
        String body = "";
        Map<String, Object> claimData = new HashMap<>();
        claimData.put("age", "20");
        claimData.put("name", "小王");
        body = JSON.toJSONString(claimData);
        System.out.println("body:" + body);
        // 模拟 query 参数
        TreeMap<String, String> map = new TreeMap<>();
        map.put("nonce", "2");
        map.put("timestamp", String.valueOf(System.currentTimeMillis()));
        map.put("cptId", "1018");
        map.put("personId", "20200516");
        map.put("remark", "小王毕业证");
        map.put("appId", "app");
        System.out.println("signString->" + JSON.toJSONString(map) + body);
        try {
            // 私钥计算签名
            byte[] signature = RsaKeyUtil.sign(JSON.toJSONString(map) + body, RsaKeyUtil.decryptBASE64(privateKey));
            String signatureStr = Base64.encodeBase64URLSafeString(signature);
            System.out.println("signature->" + signatureStr);
            // 公钥验证签名
            byte[] signatureBytes = RsaKeyUtil.decryptBASE64(signatureStr);
            boolean b = RsaKeyUtil.verify(JSON.toJSONString(map) + body, RsaKeyUtil.decryptBASE64(publicKey), signatureBytes);
            System.out.println("result->" + b);
            map.put("sign", signatureStr);
            System.out.print("请求json数据->" + JSON.toJSONString(map));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
